package bitetorrent.peersComm;

import bitetorrent.MyThread;
import bitetorrent.helper.Converter;
import bitetorrent.helper.FileManager;
import bitetorrent.helper.Helper;
import bitetorrent.model.FileDownloader;
import bitetorrent.model.Global;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author user
 */
public class P2P extends MyThread {

    static int KEEPALIVE = 0;
    static int HAVE = 4;
    static int BITFIELD = 5;
    static int REQUEST = 6;
    static int PIECE = 7;
    static int CANCEL = 8;
    protected Socket sock;
    protected String address;
    protected OutputStream out;
    protected InputStream in;
    protected String fileName;
    protected String infoHash;
    protected FileDownloader downloader;
    protected int messageLength;
    protected int messageId;
    protected byte[] messageBody;
    protected String type;

    public P2P(Socket sock) throws IOException {
        this.sock = sock;
        out = sock.getOutputStream();
        in = sock.getInputStream();
        messageLength = 0;
    }

    @Override
    public void run() {
        try {
            recieveMessage();
        } catch (InterruptedException ex) {
            Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    protected void setMessageLength() throws IOException {
        byte[] length = new byte[4];
        in.read(length);
        messageLength = Converter.bytesToInt(length) - 1;
        debug("pesan baru diterima : length " + messageLength);
    }

    protected void setMessageId() throws IOException {
        messageId = in.read();
    }

    protected void setMessageContent() throws IOException {
        int i = 0;
        messageBody = new byte[messageLength];
        while (i < messageLength) {
            messageBody[i] = (byte) in.read();
            ++i;
        }
    }

    protected void readMessage() throws IOException, InterruptedException {
        if (messageId == KEEPALIVE) {
            recieveKeepAlive();
        } else if (messageId == HAVE) {
            recieveHave();
        } else if (messageId == BITFIELD) {
            recieveBitField();
        } else if (messageId == REQUEST) {
            recieveRequest();
        } else if (messageId == PIECE) {
            recievePiece();
        } else if (messageId == CANCEL) {
            recieveCancel();
        }
    }

    /* ************************************************ */
    /* ************ RECIEVE MESSAGE METHOD ************ */
    /* ************************************************ */
    protected void recieveMessage() throws InterruptedException {
        while (loop) {
            try {
                // Set Message Length;
                setMessageLength();

                // Set Message Id;
                setMessageId();

                // Set Message Content
                setMessageContent();

                // Read Message
                readMessage();

                // Respone Message
            } catch (IOException ex) {
                Logger.getLogger(P2PClient.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    protected void recieveKeepAlive() {
    }

    protected void recieveHave() {
    }

    protected void recieveBitField() throws IOException, InterruptedException {
        // Debug
        System.out.println("Bitfield dari " + sock.getInetAddress().getHostAddress() + " diterima");

        byte[] bitField = new byte[messageLength];
        in.read(bitField);

        // Debug
        System.out.println("Isi bitfield " + Converter.bytesToString(bitField));

        downloader.setAvail(bitField);

        Thread.sleep(200);

        int nextPieceToDownload = downloader.nextPieceToDownload(address);
        sendRequest(nextPieceToDownload, 0, downloader.torrent.pieceSize);
    }

    protected void recieveRequest() throws IOException {
        int index;
        int begin;
        int length;
        byte[] temp = new byte[4];

        // Read Index
        in.read(temp);
        index = Converter.bytesToInt(temp);

        // Read begin
        in.read(temp);
        begin = Converter.bytesToInt(temp);

        // Read length
        in.read(temp);
        length = Converter.bytesToInt(temp);

        // Response
        sendPiece(index, begin, length);
    }

    protected void recievePiece() {
        
        int index;
        int begin;
        int length;

        length = messageLength-8;
        byte[] Pieces = new byte[length];
        byte[] temp = new byte[4];
        try {
            in.read(temp); //dapet index
            index = Converter.bytesToInt(temp);

            in.read(temp); //dapat begin (offset)
            begin = Converter.bytesToInt(temp);

            in.read(Pieces); //dapat pieces

            //response
            FileManager.saveFile(downloader.torrent.fileName, index * downloader.torrent.pieceSize + begin, Pieces);
            
        } catch (IOException ex) {
            Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
        }



    }

    protected void recieveCancel() {
        int index;
        int begin;
        int length;
        byte[] temp = new byte[4];
        try {
            // Read Index
            in.read(temp);
            index = Converter.bytesToInt(temp);

            // Read begin
            in.read(temp);
            begin = Converter.bytesToInt(temp);

            // Read length
            in.read(temp);
            length = Converter.bytesToInt(temp);

            //hapus koneksi
        } catch (IOException ex) {
            Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /* ************ END RECIEVE MESSAGE METHOD ************ */
    /* *********************************************** */
    /* *********** SEND METHOD DECLARATION *********** */
    /* *********************************************** */
    public void sendKeepAlive() {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                byte[] bytesLength = Converter.intToBytes(KEEPALIVE);

                try {
                    // Send
                    out.write(bytesLength);
                } catch (IOException ex) {
                    Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public void sendHave() {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                byte[] bytesLength, bytesId, bytesIndex, bytesMessage;

                bytesLength = Converter.intToBytes(5);
                bytesId = new byte[1];
                bytesId[0] = (byte) HAVE;
                bytesIndex = Converter.intToBytes(1);

                // Concat
                bytesMessage = Helper.concat(bytesLength, bytesId);
                bytesMessage = Helper.concat(bytesMessage, bytesIndex);

                try {
                    // Send
                    out.write(bytesMessage);
                } catch (IOException ex) {
                    Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        thread.start();
    }

    public void sendBitField(byte[] bytesBitField) {

        byte[] bytesLength, bytesId, bytesMessage;

        bytesId = new byte[1];
        bytesId[0] = (byte) BITFIELD;
        bytesLength = Converter.intToBytes(1 + bytesBitField.length);

        bytesMessage = Helper.concat(bytesLength, bytesId);
        bytesMessage = Helper.concat(bytesMessage, bytesBitField);

        try {
            // Send
            System.out.println("Mengirim BitFeld : " + Converter.bytesToString(bytesMessage));
            out.write(bytesMessage);
        } catch (IOException ex) {
            Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public void sendRequest(final int index, final int begin, final int length) {
        downloader.nextPieceToDownload(address);
        Thread t = new Thread(new Runnable() {

            public void run() {
                byte[] bytesId = new byte[1];
                bytesId[0] = (byte) REQUEST;
                byte[] bytesIndex = Converter.intToBytes(index);
                byte[] bytesBegin = Converter.intToBytes(begin);
                byte[] bytesLength = Converter.intToBytes(length);
                byte[] bytesMsgLength = Converter.intToBytes(17);
                byte[] bytesMessage = null;

                // Concat
                bytesMessage = Helper.concat(bytesMsgLength, bytesId);
                bytesMessage = Helper.concat(bytesMessage, bytesIndex);
                bytesMessage = Helper.concat(bytesMessage, bytesBegin);
                bytesMessage = Helper.concat(bytesMessage, bytesLength);
                try {
                    // Send
                    out.write(bytesMessage);
                } catch (IOException ex) {
                    Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public void sendPiece(final int index, final int begin, final int length) {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                byte[] bytesId = new byte[1];
                bytesId[0] = (byte) PIECE;
                byte[] bytesIndex = Converter.intToBytes(index);
                byte[] bytesBegin = Converter.intToBytes(begin);
                byte[] bytesBlock = Global.fileDownloads.getPiece(fileName, index, begin, length);
                byte[] bytesLength = Converter.intToBytes(7 + bytesBlock.length);
                byte[] bytesMessage = null;

                // Concat
                bytesMessage = Helper.concat(bytesLength, bytesId);
                bytesMessage = Helper.concat(bytesMessage, bytesIndex);
                bytesMessage = Helper.concat(bytesMessage, bytesBegin);
                bytesMessage = Helper.concat(bytesMessage, bytesBlock);
                try {
                    // Send
                    out.write(bytesMessage);
                } catch (IOException ex) {
                    Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        thread.start();
    }

    public void sendCancel(final int index, final int begin, int length) {
        Thread thread = new Thread(new Runnable() {

            public void run() {
                byte[] bytesId = new byte[1];
                bytesId[0] = (byte) CANCEL;
                byte[] bytesIndex = Converter.intToBytes(index);
                byte[] bytesBegin = Converter.intToBytes(begin);
                byte[] bytesLength = Converter.intToBytes(13);
                byte[] bytesMessage = null;

                // Concat
                bytesMessage = Helper.concat(bytesLength, bytesId);
                bytesMessage = Helper.concat(bytesMessage, bytesIndex);
                bytesMessage = Helper.concat(bytesMessage, bytesBegin);

                try {
                    // Send
                    out.write(bytesMessage);
                } catch (IOException ex) {
                    Logger.getLogger(P2P.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });

        thread.start();
    }

    /* ********** END SEND METHOD DECLARATION ********** */
    protected byte[] getBitField(String fileName) {
        return Global.fileDownloads.getBitField(fileName);
    }

    protected byte[] getPiece(String fileName, int index, int begin) {
        return Global.fileDownloads.getPiece(fileName, index, begin, begin);
    }


    public void debug(String msg) {
        System.out.println(type + " -> " + msg);
    }
}
